/**
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 */
package org.evosuite.continuous.job.schedule;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import org.evosuite.continuous.job.JobDefinition;
import org.evosuite.continuous.job.JobScheduler;
import org.evosuite.continuous.project.ProjectStaticData;
import org.evosuite.continuous.project.ProjectStaticData.ClassInfo;

/**
 * CUTs with more branches will be given more time (ie search budget)
 * 
 * @author arcuri
 *
 */
public class BudgetSchedule extends OneTimeSchedule{

	public BudgetSchedule(JobScheduler scheduler) {
		super(scheduler);
	}

	@Override
	protected List<JobDefinition> createScheduleOnce() {
		
		ProjectStaticData data = scheduler.getProjectData();

		if(data.getTotalNumberOfBranches() == 0){
			return new SimpleSchedule(scheduler).createScheduleOnce();
		}

		int maximumBudgetPerCore = 60 * scheduler.getConfiguration().timeInMinutes;
		
		/*
		 * the total budget we need to choose how to allocate
		 */
		int totalBudget =  maximumBudgetPerCore * scheduler.getConfiguration().getNumberOfUsableCores(); 

		/*
		 * a part of the budget is fixed, as each CUT needs a minimum
		 * of it. 
		 */
		int minTime = 60 * scheduler.getConfiguration().minMinutesPerJob * data.getTotalNumberOfTestableCUTs();
		
		/*
		 * this is what left from the minimum allocation, and that now we can
		 * choose how best to allocate
		 */
		int extraTime = totalBudget - minTime;

		/*
		 * check how much time we can give extra for each branch in a CUT 
		 */
		double timePerBranch = (double)extraTime / (double)data.getTotalNumberOfBranches();
		
		int totalLeftOver = 0;
		
		List<JobDefinition> jobs = new LinkedList<>();

		for(ClassInfo info : data.getClassInfos()){
			if(!info.isTestable()){
				continue;
			}
			/*
			 * there is a minimum that is equal to all jobs,
			 * plus extra time based on number of branches
			 */
			int budget = 60 * scheduler.getConfiguration().minMinutesPerJob + 
					(int)(timePerBranch * info.numberOfBranches);
			
			if(budget > maximumBudgetPerCore){
				/*
				 * Need to guarantee that no job has more than 
				 * maximumBudgetPerCore regardless of number of cores
				 */
				totalLeftOver += (budget - maximumBudgetPerCore);
				budget = maximumBudgetPerCore;
			}
			
			JobDefinition job = new JobDefinition(
					budget, scheduler.getConfiguration().getConstantMemoryPerJob(), info.getClassName(), 0, null, null);
			jobs.add(job);
			
		}
		
		if(totalLeftOver > 0){
			/*
			 * we still have some more budget to allocate
			 */
			distributeExtraBudgetEvenly(jobs,totalLeftOver,maximumBudgetPerCore);
		}
		
		/*
		 * using scheduling theory, there could be different
		 * best orders to maximize CPU usage.
		 * Here, at least for the time being, for simplicity
		 * we just try to execute the most expensive jobs
		 * as soon as possible
		 */
		
		Collections.sort(jobs, (a, b) -> {
            /*
             * the job with takes most time will be "lower".
             * recall that sorting is in ascending order
             */
            return b.seconds - a.seconds;
        });
		
		
		return jobs;
	}
}
